package etcd

import (
	"context"
	"encoding/json"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"log"
	"strings"
	"time"
)

// Discovery 发现器
type Discovery struct {
	cli       *clientv3.Client
	info      *NodeInfo
	nodeCache *LocalNodeCache
}

func NewDiscovery(cli *clientv3.Client) *Discovery {
	return &Discovery{
		cli:  cli,
		info: &NodeInfo{},
		nodeCache: &LocalNodeCache{
			nodes: make(map[string][]*NodeInfo),
		},
	}
}

// Watch etcd watch功能监听结点信息变更更新本地缓存信息
func (d *Discovery) Watch() {
	watcher := clientv3.NewWatcher(d.cli)
	watchChan := watcher.Watch(context.Background(), "services/", clientv3.WithPrefix())
	for {
		select {
		case resp := <-watchChan:
			d.watchEvent(resp.Events)
		}
	}
}

func (d *Discovery) watchEvent(evs []*clientv3.Event) {
	for _, ev := range evs {
		log.Printf("event:%+v\n", ev)
		switch ev.Type {
		case clientv3.EventTypePut:
			node := &NodeInfo{}
			err := json.Unmarshal(ev.Kv.Value, node)
			if err != nil {
				log.Printf("Discovery watchEvent json.Unmarshal err:%+v\n", err)
				continue
			}
			if d.nodeCache.hasSvr(node.SvrName) {
				log.Printf("只缓存本地已存在的svrvice：%v\n", node)
				d.nodeCache.AddNode(node)
				return
			}
			log.Printf(fmt.Sprintf("watchEvent put svr:%v new node:%s", node.SvrName, string(ev.Kv.Value)))
		case clientv3.EventTypeDelete:
			key := string(ev.Kv.Key)
			keys := strings.Split(key, "/")
			if len(keys) != 3 {
				log.Printf("service key：%v illegal \n", key)
				continue
			}
			d.nodeCache.DelNode(keys[1], keys[2])
			log.Printf(fmt.Sprintf("watchEvent delete svr:%v", string(ev.Kv.Key)))
		}
	}
}

// GetNode 服务发现，根据服务名随机获取一个服务实例，优先去本地缓存查，如果本地缓存没有则去etcd上查询结点信息，并且缓存起来，再从缓存中获取
func (d *Discovery) GetNode(svrName string) *NodeInfo {
	node := d.nodeCache.GetNode(svrName)
	if node != nil {
		log.Printf("本地缓存找到node:%v\n", node)
		return node
	}
	ctx, _ := context.WithTimeout(context.Background(), 3*time.Second)
	resp, err := d.cli.Get(ctx, getRegKeyPrefix(svrName), clientv3.WithPrefix())
	if err != nil {
		log.Printf("Discovery get kv err:%+v\n", err)
		return nil
	}
	for _, v := range resp.Kvs {
		node := &NodeInfo{}
		err = json.Unmarshal(v.Value, node)
		if err != nil {
			log.Printf("Discovery json.Unmarshal err:%+v", err)
			continue
		}
		d.nodeCache.AddNode(node)
		log.Printf("Discovery add node:%+v", node)
	}
	return d.nodeCache.GetNode(svrName)
}
